package com.xp.basic.niodemo;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

public class MultiplexerTimeServer implements Runnable{

    private Selector  selector;

    private ServerSocketChannel  serverSocketChannel;

    private volatile boolean stop;

    public MultiplexerTimeServer(int port) {
        try{
            //初始化多路复用器
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            //将serversocketchannel 设置为异步非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //系统资源初始化
            serverSocketChannel.socket().bind(new InetSocketAddress(port),1024);
            //将serversocketchannel 注册到Selector 监听 SelectionKey.OP_ACCEPT 操作位 如果初始化失败（端口被占用） 则退出
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("The time server is start in port: "+port);
        }catch (Exception e){
            e.printStackTrace();
            System.exit(1);
        }
    }


    public void stop(){
        this.stop = true;
    }


    /**
     * Runs this operation.
     */
    public void run() {

        while (!stop){
            try{
                //循环便利 select 休眠时间为1秒，最多阻塞 1秒
                selector.select(1000);
                //当有处于就绪状态的Channel 时，selector 就返回 selectedKeys 集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                ByteBuffer.allocateDirect(1024);
                Iterator<SelectionKey> it = selectionKeys.iterator();
                SelectionKey  key =  null;
                //对已经就绪的 selectionKeys（channel 集合 ）进行迭代，可以进行网络的异步读写操作
                while (it.hasNext()){
                    key = it.next();
                    it.remove();
                    try{
                        handleInput(key);
                    }catch (Exception e){
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        //多路复用器关闭后，所有注册在上面的 Channel 和 Pipe 等资源都会被自动去注册并关闭，所以不需要重复释放资源
        if (selector != null) {
            try {
                selector.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

    }

    private void handleInput(SelectionKey key) throws IOException {

        if (key.isValid()) {

            //根据 key 的操作位进行判断即可以知道网络事件的类型，

            if (key.isAcceptable()) {
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                //接收客户端的请求链接并创建 SocketChannel 实例，完成这步操作相当于完成了TCP 的三次握手，TCP物理链路正式建立
                SocketChannel sc = ssc.accept();
                //将新创建的 SocketChannel 设置为异步非阻塞，同时可以对其TCP参数进行设置，
                sc.configureBlocking(false);
                sc.register(selector,SelectionKey.OP_READ);
            }

            //读取客户端的请求消息
            if (key.isReadable()) {
                SocketChannel sc = (SocketChannel) key.channel();
                //首先创建一个 ByteBuffer，由于事先是无法知道客户端发送的码流大小，所以这里暂时开辟一个 1M 的缓冲区
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                //然后调用 SocketChannel 的reda 方法读取请求码流，由于上面已经将 SocketChannel 设置为异步非阻塞模式，所以他的 read 是非阻塞的
                //使用返回值进行判断，看到读取的字节数，返回值有三种可能的结果，
                // 大于零：读取到了字节，对字节进行编码
                // 等于零：没有读取到字节，属于正常场景，忽略
                // 返回值等于 -1： 链路已经关闭，需要关闭 SocketChannel 释放资源
                int readBytes = sc.read(readBuffer);
                if (readBytes > 0) {
                    //当读取到码流之后，我们进行解码，首先对readBuffer进行flip操作，
                    // 它的作用是将目前缓冲区的当前的limit 设置为 position，position 设置为 0
                    // 用于后续对缓冲区的读取操作
                    readBuffer.flip();
                    //根据缓冲区可读的字节数个数创建数组
                    byte[] bytes = new byte[readBuffer.remaining()];
                    //将缓冲区可读的字节数组复制到新创建的字节数组中
                    readBuffer.get(bytes);
                    //最后调用字符串的构造函数创建请求消息体
                    String body = new String(bytes, "UTF-8");
                    //打印
                    System.out.println("The time server receive oder: "+ body);
                    // 如果请求是 QUERY TIME ORDER 则把服务器的当前时间编码后返回给客户端
                    String currentTime = "QUERY TIME ORDER ".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() :"BAD ORDER";
                    doWrite(sc,currentTime);
                }else if(readBytes < 0){
                    //对链路关闭
                    key.cancel();
                    sc.close();
                }else{
                    // 读到 0 字节 忽略
                }
            }
        }
    }


    /**
     * 将应答消息发送给客户端
     * @param channel
     * @param response
     * @throws IOException
     */
    private void doWrite(SocketChannel channel,String response) throws IOException {
        if (response != null && response.trim().length() > 0) {
            //将字符串编码成字节数组
            byte[] bytes = response.getBytes();
            //根据字节数组的容量创建 ByteBuffer
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            //将字节数组复制到缓冲区
            writeBuffer.put(bytes);
            //对缓冲区进行 flip 操作
            writeBuffer.flip();
            // 调用SocketChannel 将缓冲区中的字节数组发送出去
            channel.write(writeBuffer);
        }
    }
}
